package drds.plus.sequence.impl;

import drds.plus.sequence.Range;
import drds.plus.sequence.Sequence;
import drds.plus.sequence.SequenceDao;
import drds.plus.sequence.SequenceException;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j

public class SequenceImpl implements Sequence {

    private final Lock lock = new ReentrantLock();

    private SequenceDao sequenceDao;

    /**
     * 序列名称
     */
    private String name;
    private volatile Range currentRange;

    public SequenceImpl(SequenceDao sequenceDao, String name) {
        this.sequenceDao = sequenceDao;
        this.name = name;
    }

    public long nextValue() throws SequenceException {
        if (currentRange == null) {
            lock.lock();
            try {
                if (currentRange == null) {
                    currentRange = sequenceDao.nextRange(name);
                }
            } finally {
                lock.unlock();
            }
        }

        long value = currentRange.getAndIncrement();
        if (value == -1) {
            lock.lock();
            try {
                for (; ; ) {
                    if (currentRange.isOver()) {
                        currentRange = sequenceDao.nextRange(name);
                    }
                    value = currentRange.getAndIncrement();
                    if (value == -1) {
                        continue;
                    }
                    break;
                }
            } finally {
                lock.unlock();
            }
        }
        if (value < 0) {
            throw new SequenceException("sequence value overflow, value = " + value);
        }
        return value;
    }

    public long nextValue(int size) throws SequenceException {
        if (size > this.sequenceDao.getStep()) {
            throw new SequenceException("batch size > sequence step step, please change batch size or sequence inner step");
        }
        if (currentRange == null) {
            lock.lock();
            try {
                if (currentRange == null) {
                    currentRange = sequenceDao.nextRange(name);
                }
            } finally {
                lock.unlock();
            }
        }
        long value = currentRange.getBatch(size);
        if (value == -1) {
            lock.lock();
            try {
                for (; ; ) {
                    if (currentRange.isOver()) {
                        currentRange = sequenceDao.nextRange(name);
                    }
                    value = currentRange.getBatch(size);
                    if (value == -1) {
                        continue;
                    }
                    break;
                }
            } finally {
                lock.unlock();
            }
        }
        if (value < 0) {
            throw new SequenceException("sequence value overflow, value = " + value);
        }
        return value;
    }

}
